import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
/**
 * SNMPPoller runs all the polling on the network
 * @author skinner@kth.se
 * @author aidi@kth.se
 */
class SNMPPoller {
	static int threadsCompleted = 0;
	static int roundNum = 0;
	
	// Every round has a list for the values from all routers who respond
	//	These are lists of these rounds, using the round number as the key
	static Hashtable<Integer, List<Long>> xRounds = new Hashtable<Integer, List<Long>>();;
	static Hashtable<Integer, List<Long>> yRounds = new Hashtable<Integer, List<Long>>();

	final static int BUFFER_TIME = 1000 * 5;
	
	public static void reset() {
		for (int i=0; i<roundNum; i++) {
			if ( xRounds.get(i) != null) {
				xRounds.get(i).clear();
			}
			if ( yRounds.get(i) != null) {
				yRounds.get(i).clear();
			} 
		}
		
		roundNum = 0;
	}
	
	public static void poll(List<Router> rtrs, int interval, int timespan, int k, int repeats, boolean useOutputFile) {

		long start = System.currentTimeMillis();
		
		SNMPUtils su = new SNMPUtils();
		su.open();
		
		int numThreads = 0;
		while (true) {
			for (Router r: rtrs) {
				PollingThread pt = new PollingThread(su, r, SNMPUtils.OIDS.get("ifInOctets"), SNMPUtils.OIDS.get("ifInUcastPkts"), roundNum);
				pt.start();	
				numThreads++;
			}

			
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    		roundNum++;			
    		
			// Exit loop after timespan
			if (System.currentTimeMillis() - start > timespan) {
    			break;
    		}
		}
		
		/* Wait for threads to complete */
		while (true) {
			if (threadsCompleted == numThreads) {
			    	break;
			}
		}

		su.close();
		System.out.println("Done polling (" +roundNum + " rounds):\t" + (System.currentTimeMillis() - start)/1000 + "s");
		
		Clusterer c = new Clusterer(xRounds, yRounds, k, repeats, 2, useOutputFile);
	}
	
	public static void onlinePoll(List<Router> rtrs, int interval, int w, int k, int repeats, boolean useOutputFile) {

		long start = System.currentTimeMillis();
		roundNum = 0;
		SNMPUtils su = new SNMPUtils();
		su.open();
		
		int numThreads = 0;
		while (true) {
			for (Router r: rtrs) {
				PollingThread pt = new PollingThread(su, r, SNMPUtils.OIDS.get("ifInOctets"), SNMPUtils.OIDS.get("ifInUcastPkts"), roundNum);
				pt.start();	
				numThreads++;
			}

			
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    		roundNum++;			
    		
    		if (roundNum % (w + 1) == 0) {
    			/* Wait for threads to complete */
    			while (true) {
    				if (threadsCompleted == numThreads) {
    				    	break;
    				}
    			}
    			
    			System.out.println("\nTime: " + ((int) (System.currentTimeMillis() - start)/1000) + "s");
    			/* We want next polling of w global-set to be started immedietly, 
    			 * thus should use thread so the next polling of w global-sets dont need to wait until Clusterer.cluster finish
    			 * putting DBI calculation in that thread would also be possible
    			 */
    			Clusterer c = new Clusterer(xRounds, yRounds, k, repeats, 3, useOutputFile);
        		reset();
    		}
		}
	}
	
	/**
	 * Synchronized method to add an X value from a given round to the SNMPPoller's list from the PollingThreads
	 * @param round
	 * @param x
	 */
	public static synchronized void addXRound(int round, long x) {
		if(xRounds.get(round) == null) {
			List<Long> newList = new ArrayList<Long>();
			newList.add(x);
			xRounds.put(round, newList);
		} else {
			xRounds.get(round).add(x);
		}
	}
	
	/**
	 * Synchronized method to add an Y value from a given round to the SNMPPoller's list from the PollingThreads
	 * @param round
	 * @param y
	 */
	public static synchronized void addYRound(int round, long y) {
		if(yRounds.get(round) == null) {
			List<Long> newList = new ArrayList<Long>();
			newList.add(y);
			yRounds.put(round, newList);
		} else {
			yRounds.get(round).add(y);
		}
	}
	
	/**
	 * Synchronized method to keep track of how many threads have finished their poll
	 * @param round
	 * @param x
	 */
	public static synchronized void addThreadCompleted() {
		threadsCompleted++;
	}
	
}

/***
 * Threaded class to run polls asynchronously (more efficiently)
 * 
 * @author skinner@kth.se
 * @author skinner@kth.se
 */
class PollingThread extends Thread {
	
	private SNMPUtils su;
	private Router r;
	private String oidX, oidY;	
	private int roundNum;
	
	/**
	 * Construct the thread
	 * @param su		- A var to run SNMP utilities with
	 * @param r			- The router we are polling
	 * @param x			- The OID string for the x value
	 * @param y			- The OID string for the y value
	 * @param roundNum	- The round number this thread is associated with
	 */
	public PollingThread(SNMPUtils su, Router r, String x, String y, int roundNum) {
		this.r = r;
		this.oidX = x;
		this.oidY = y;
		this.roundNum = roundNum; 
		this.su = su;
	}
	
	/**
	 * Execution of the poll
	 */
	public void run() {		
		try {
			/* Loop over every interface that has an IP address and sum their values */
			long x = 0;
			for(int i : r.getInterfaces().keySet()) {
				if (r.getInterfaces().get(i).getIp() != null) {
					x += (Long) su.getVar(r.getLocalIps().get(0), oidX + "." + i).toValue();
				}
			}
			SNMPPoller.addXRound(roundNum, x);
		}  catch (NullPointerException npe) {
			/* Don't return any value, unless we received values from all interfaces (since we are using sums everywhere else) */
			System.out.println("NPE in Polling Thread.run() - X - Router: " + r.getHostname() + "\tround:" + roundNum);
		}
		
		try {
			/* Loop over every interface that has an IP address  and sum their values */
			long y = 0;
			for(int i : r.getInterfaces().keySet()) {
				if (r.getInterfaces().get(i).getIp() != null) {
					y += (Long) su.getVar(r.getLocalIps().get(0), oidY + "." + i).toValue();
				}
			}
			SNMPPoller.addYRound(roundNum, y);
		}  catch (NullPointerException npe) {
			/* Don't return any value, unless we received values from all interfaces (since we are using sums everywhere else) */
			System.out.println("NPE in Polling Thread.run() - Y - Router: " + r.getHostname() + "\tround:" + roundNum);
		}
		
		/* Indicate that this thread has finished its poll event */
		SNMPPoller.addThreadCompleted();
	}
}
